﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SCMS.Model;
using System.Transactions;
using SCMS.CoreBusinessLogic.NotificationsManager;
using SCMS.CoreBusinessLogic._ExchangeRate;
using SCMS.CoreBusinessLogic.Budgeting;

namespace SCMS.CoreBusinessLogic.ExpenseClaim
{
    public class ExpenseClaimService : IExpenseClaimService
    {
        private INotificationService notificationService;
        private IExchangeRateService exchangeRateService;
        private IBudgetService budgetService;

        public ExpenseClaimService(INotificationService notificationService, IExchangeRateService exchangeRateService, IBudgetService budgetService)
        {
            this.notificationService = notificationService;
            this.exchangeRateService = exchangeRateService;
            this.budgetService = budgetService;
        }

        public bool SaveExpenseClaimItem(Model.ExpenseItem ExpsItemEntity)
        {
            using (var dbContext = new SCMSEntities())
            {
                using (TransactionScope scope = new TransactionScope())
                {
                    try
                    {
                        Model.ExpenseRequest EC = dbContext.ExpenseRequests.First(p => p.Id == ExpsItemEntity.ExpenseId);
                        ExpsItemEntity.ConvertedAmount = ExpsItemEntity.ItemAmount;
                        if (ExpsItemEntity.Id.Equals(Guid.Empty))
                        {
                            ExpsItemEntity.Id = Guid.NewGuid();
                            dbContext.ExpenseItems.AddObject(ExpsItemEntity);
                            if ((dbContext.SaveChanges() > 0))
                            {                                
                                EC.TotalValue += ExpsItemEntity.ItemAmount;                                
                                if ((dbContext.SaveChanges() > 0)) { scope.Complete(); return true; } else { scope.Dispose(); return false; }
                            }
                            else { scope.Dispose(); return false; }
                        }
                        else
                        {
                            var ecfItem = dbContext.ExpenseItems.First(p => p.Id == ExpsItemEntity.Id);
                            EC.TotalValue -= ecfItem.ItemAmount;
                            EC.TotalValue += ExpsItemEntity.ItemAmount;
                            ecfItem.BudgetLineId = ExpsItemEntity.BudgetLineId;
                            ecfItem.ConvertedAmount = ExpsItemEntity.ConvertedAmount;
                            ecfItem.ItemAmount = ExpsItemEntity.ItemAmount;
                            ecfItem.ItemDescription = ExpsItemEntity.ItemDescription;                            
                            
                            if ((dbContext.SaveChanges() > 0))
                            {
                                scope.Complete();
                                return true;
                            }
                            else { scope.Dispose(); return false; }
                        }
                    }
                    catch (Exception ex)
                    {
                        scope.Dispose();
                        throw ex;
                    }
                }
            }
        }

        public string GenerateUniquNumber(CountryProgramme cp)
        {
            string code = "ECF/DRC/" + cp.Country.ShortName + "/";
            string refNumber = "";
            long count = 1;
            using (var dbContext = new SCMSEntities())
            {
                var total = dbContext.ExpenseRequests.Where(p => p.CountryProgrammeId == cp.Id && p.IsSubmitted == true).Count();
                count = total;
                Model.ExpenseRequest m = null;
                do
                {
                    count++;
                    if (count < 10000)
                    {
                        if (count < 10)
                            refNumber = code + "0000" + count;
                        if (count < 100 & count >= 10)
                            refNumber = code + "000" + count;
                        if (count < 1000 & count >= 100)
                            refNumber = code + "00" + count;
                        if (count < 10000 & count >= 1000)
                            refNumber = code + "0" + count;
                    }
                    m = dbContext.ExpenseRequests.FirstOrDefault(p => p.RefNumber == refNumber);
                } while (m != null);
                return refNumber;
            }
        }

        public List<Model.ExpenseItem> GetExpenseItems(Guid ExpsClaimId)
        {
            using (var db = new SCMSEntities())
            {
                List<ExpenseItem> ExpsItems = db.ExpenseItems.Where(p => p.ExpenseId == ExpsClaimId).ToList();
                foreach (ExpenseItem item in ExpsItems)
                {
                    Model.ProjectDonor pb = item.ProjectBudget.BudgetCategory.ProjectDonor;
                    Model.ExpenseRequest er = item.ExpenseRequest;
                }
                return ExpsItems;
            }
        }

        public List<ExpenseRequest> GetExpenseClaimsForPosting(Guid countryProgId, SystemUser currentUser)
        {
            using (var context = new SCMSEntities())
            {
                var approvers = context.Approvers.Where(a => a.CountryProgrammeId == countryProgId && a.ActivityCode == NotificationHelper.ecfCode && a.ActionType == NotificationHelper.postFundsCode
                    && (a.UserId == currentUser.Id || a.AssistantId == currentUser.Id));
                var financeLimit = context.SystemUsers.FirstOrDefault(s => s.Id == currentUser.Id).Staff.FinanceLimit;
                if (financeLimit == null)
                    return new List<ExpenseRequest>();
                if (approvers.ToList().Count == 0)
                    return new List<ExpenseRequest>();
                if (approvers.Where(a => a.ProjectDonorId == null).Count() > 0)
                    return context.ExpenseRequests.Where(e => e.CountryProgrammeId == countryProgId && e.IsAuthorized == true && e.FundsPosted == false
                        && (e.MBTotal.Value <= financeLimit.Limit || financeLimit.Limit == 0)).ToList();
                List<ExpenseRequest> expenseRequests = new List<ExpenseRequest>();
                foreach (var approver in approvers.ToList())
                {
                    var ecfs = context.ExpenseRequests.Where(e => e.CountryProgrammeId == countryProgId && e.IsAuthorized == true && e.FundsPosted == false
                        && (e.MBTotal.Value <= financeLimit.Limit || financeLimit.Limit == 0) 
                        && e.ExpenseItems.Where(i => i.ProjectBudget.BudgetCategory.ProjectDonorId.Value == approver.ProjectDonorId.Value).Count() > 0).ToList();
                    foreach (var ecf in ecfs)
                    {
                        if (!EcfExistsInList(ecf, expenseRequests))
                            expenseRequests.Add(ecf);
                    }
                }
                return expenseRequests;
            }
        }

        private bool EcfExistsInList(ExpenseRequest ecf, List<ExpenseRequest> ecfList)
        {
            foreach (var e in ecfList)
            {
                if (e.Equals(ecf))
                    return true;
            }
            return false;
        }

        public List<ExpenseRequest> GetExpenseClaimsForReview(SystemUser currentUser)
        {
            try
            {
                using (var context = new SCMSEntities())
                {
                    List<Model.ExpenseRequest> expenseClaims = new List<Model.ExpenseRequest>();
                    context.SystemUsers.Attach(currentUser);
                    var approvers = context.Approvers.Where(a => a.UserId == currentUser.Id && a.ActivityCode == NotificationHelper.ecfCode && a.ActionType == NotificationHelper.reviewCode).ToList();
                    var delegateApprovers = context.Approvers.Where(a => a.AssistantId == currentUser.Id && a.ActivityCode == NotificationHelper.ecfCode && a.ActionType == NotificationHelper.reviewCode).ToList();
                    if (approvers != null)
                    {
                        foreach (var approver in approvers)
                        {
                            var ecfList = context.ExpenseRequests.Where(e => e.CountryProgrammeId == currentUser.Staff.CountrySubOffice.CountryProgrammeId && (e.IsSubmitted == true && e.IsReviewed == false && e.IsRejected == false)
                                && e.Notifications.Where(n => (Guid)n.ExpenseClaimId == e.Id && n.IsRespondedTo == false && n.SentToDelegate == false && n.ApproverId == approver.Id).Count() > 0).ToList();
                            foreach (var ecf in ecfList)
                            {
                                expenseClaims.Add(ecf);
                            }
                        }
                    }
                    if (delegateApprovers != null)
                    {
                        foreach (var delegateApprover in delegateApprovers)
                        {
                            var ecfList = context.ExpenseRequests.Where(e => e.CountryProgrammeId == currentUser.Staff.CountrySubOffice.CountryProgrammeId && (e.IsSubmitted == true && e.IsReviewed == false && e.IsRejected == false)
                                && e.Notifications.Where(n => (Guid)n.ExpenseClaimId == e.Id && n.IsRespondedTo == false && n.SentToDelegate == true && n.ApproverId == delegateApprover.Id).Count() > 0).ToList();
                            foreach (var ecf in ecfList)
                            {
                                expenseClaims.Add(ecf);
                            }
                        }
                    }
                    return expenseClaims;
                }
            }
            catch (Exception ex)
            {
                return new List<Model.ExpenseRequest>();
            }
        }

        public List<ExpenseRequest> GetExpenseClaimsForAuth(SystemUser currentUser)
        {
            try
            {
                using (var context = new SCMSEntities())
                {
                    List<Model.ExpenseRequest> expenseClaims = new List<Model.ExpenseRequest>();
                    context.SystemUsers.Attach(currentUser);
                    var approvers = context.Approvers.Where(a => a.UserId == currentUser.Id && a.ActivityCode == NotificationHelper.ecfCode && a.ActionType == NotificationHelper.authorizationCode).ToList();
                    var delegateApprovers = context.Approvers.Where(a => a.AssistantId == currentUser.Id && a.ActivityCode == NotificationHelper.ecfCode && a.ActionType == NotificationHelper.authorizationCode).ToList();
                    if (approvers != null)
                    {
                        foreach (var approver in approvers)
                        {
                            var ecfList = context.ExpenseRequests.Where(e => e.CountryProgrammeId == currentUser.Staff.CountrySubOffice.CountryProgrammeId && (e.IsReviewed == true && e.IsRejected == false && e.IsAuthorized == false)
                                && e.Notifications.Where(n => (Guid)n.ExpenseClaimId == e.Id && n.IsRespondedTo == false && n.SentToDelegate == false && n.ApproverId == approver.Id).Count() > 0).ToList();
                            foreach (var ecf in ecfList)
                            {
                                expenseClaims.Add(ecf);
                            }
                        }
                    }
                    if (delegateApprovers != null)
                    {
                        foreach (var delegateApprover in delegateApprovers)
                        {
                            var ecfList = context.ExpenseRequests.Where(e => e.CountryProgrammeId == currentUser.Staff.CountrySubOffice.CountryProgrammeId && (e.IsReviewed == true && e.IsRejected == false && e.IsAuthorized == false)
                                && e.Notifications.Where(n => (Guid)n.ExpenseClaimId == e.Id && n.IsRespondedTo == false && n.SentToDelegate == true && n.ApproverId == delegateApprover.Id).Count() > 0).ToList();
                            foreach (var ecf in ecfList)
                            {
                                expenseClaims.Add(ecf);
                            }
                        }
                    }
                    return expenseClaims;
                }
            }
            catch (Exception ex)
            {
                return new List<Model.ExpenseRequest>();
            }
        }

        public ExpenseRequest GetExpenseClaimById(Guid Id)
        {
            using (var context = new SCMSEntities())
            {
                var ec = context.ExpenseRequests.FirstOrDefault(e => e.Id == Id);
                var currr = ec.Currency;
                currr = ec.Currency1;
                currr = ec.Currency2;
                var person = ec.Staff1.Person;
                var desg = ec.Staff1.Designation;
                person = ec.Staff2.Person;
                desg = ec.Staff2.Designation;
                return ec;
            }
        }

        public List<AccountCode> GetAccountCodes(Guid countryProgId)
        {
            using (var context = new SCMSEntities())
            {
                return context.AccountCodes.Where(a => a.CountryProgrammeId == countryProgId).OrderBy(a => a.Code).ToList();
            }
        }

        public ExpenseItem GetExpenseItemById(Guid Id)
        {
            using (var context = new SCMSEntities())
            {
                var ecfItem = context.ExpenseItems.FirstOrDefault(e => e.Id == Id);
                Model.ProjectDonor pb = ecfItem.ProjectBudget.BudgetCategory.ProjectDonor;
                Model.ExpenseRequest er = ecfItem.ExpenseRequest;
                return ecfItem;
            }
        }

        public bool SaveECF(ExpenseRequest ecf)
        {
            using (var context = new SCMSEntities())
            {
                var ecfEntity = context.ExpenseRequests.FirstOrDefault(e => e.Id == ecf.Id);
                if (ecfEntity == null)
                {
                    ecf.Id = Guid.NewGuid();
                    ecf.CurrencyConvertedTo = ecf.ExpenseCurrencyId;
                    ecf.ExchangeRate = 1;
                    context.ExpenseRequests.AddObject(ecf);
                }
                else
                {
                    ecfEntity.ClaimBy = ecf.ClaimBy;
                    ecfEntity.RequestDate = ecf.RequestDate;
                    ecfEntity.ExpenseCurrencyId = ecf.ExpenseCurrencyId;
                    ecfEntity.CurrencyConvertedTo = ecf.ExpenseCurrencyId;
                }
                return context.SaveChanges() > 0;
            }
        }

        public bool SaveApprovedECF(ExpenseRequest ecf)
        {
            using (var context = new SCMSEntities())
            {
                context.ExpenseRequests.Attach(ecf);
                context.ObjectStateManager.ChangeObjectState(ecf, System.Data.EntityState.Modified);
                return context.SaveChanges() > 0;
            }
        }

        public bool SaveApprovedECFItem(ExpenseItem ecfItem)
        {
            using (var context = new SCMSEntities())
            {
                context.ExpenseItems.Attach(ecfItem);
                context.ObjectStateManager.ChangeObjectState(ecfItem, System.Data.EntityState.Modified);
                return context.SaveChanges() > 0;
            }        
        }

        public bool CommitFunds(ExpenseRequest ecf)
        {
            decimal commitAmount = 0;
            using (var context = SCMSEntities.Define())
            {
                using (TransactionScope scope = new TransactionScope())
                {
                    try
                    {
                        var ecfItems = context.ExpenseItems.Where(a => a.ExpenseId == ecf.Id).ToList();
                        foreach (var ecfItem in ecfItems)
                        {
                            commitAmount = (decimal)exchangeRateService.GetForeignCurrencyValue(ecfItem.ProjectBudget.BudgetCategory.ProjectDonor.Currency, ecfItem.ExpenseRequest.Currency1, ecfItem.ConvertedAmount, ecfItem.ExpenseRequest.CountryProgramme.Id);
                            var bc = new BudgetCommitment();
                            bc.Id = Guid.NewGuid();
                            bc.AmountCommitted = commitAmount;
                            bc.BudgetLineId = ecfItem.BudgetLineId;
                            bc.DateCommitted = DateTime.Now;
                            bc.ExpenseItemId = ecfItem.Id;
                            context.BudgetCommitments.AddObject(bc);
                            ecfItem.ProjectBudget.TotalCommitted += commitAmount;
                        }
                        if (context.SaveChanges() > 0)
                        {
                            scope.Complete();
                            return true;
                        }
                        else
                        {
                            scope.Dispose();
                            return false;
                        }
                    }
                    catch (Exception ex)
                    {
                        scope.Dispose();
                        throw ex;
                    }
                }
            }
        }

        public bool EffectPosting(ExpenseRequest ecf, Staff poster)
        {
            using (var context = SCMSEntities.Define())
            {
                using (TransactionScope scope = new TransactionScope())
                {
                    try
                    {
                        var ecfCommits = context.BudgetCommitments.Where(b => b.ExpenseItem.ExpenseId == ecf.Id).ToList();
                        foreach (var ecfCommit in ecfCommits)
                        {
                            var pb = ecfCommit.ProjectBudget;
                            pb.TotalCommitted -= ecfCommit.AmountCommitted;

                            var bp = new BudgetPosting();
                            bp.Id = Guid.NewGuid();
                            bp.AmountPosted = ecfCommit.AmountCommitted;
                            bp.DatePosted = DateTime.Now;
                            bp.PostedBy = poster.Id;
                            bp.ExpenseItemId = ecfCommit.ExpenseItemId;
                            pb.TotalPosted += bp.AmountPosted;

                            //Delete commitment and add posting
                            context.BudgetCommitments.DeleteObject(ecfCommit);
                            context.BudgetPostings.AddObject(bp);
                        }

                        var expenseClaim = context.ExpenseRequests.FirstOrDefault(a => a.Id == ecf.Id);
                        expenseClaim.FundsPosted = true;
                        expenseClaim.PostedBy = poster.Id;
                        expenseClaim.PostedOn = DateTime.Now;

                        if (context.SaveChanges() > 0)
                        {
                            scope.Complete();
                            return true;
                        }
                        else
                        {
                            scope.Dispose();
                            return false;
                        }
                    }
                    catch (Exception ex)
                    {
                        scope.Dispose();
                        throw ex;
                    }
                }
            }
        }

        // <summary>
        /// Send notification to requestor and project managers of the affected budget lines
        /// </summary>
        /// <param name="rfa"></param>
        public void NotifyAffected(ExpenseRequest ecf)
        {
            using (var context = new SCMSEntities())
            {
                //Get ProjectBLCount list order by highest budget line count to least for this RFA
                List<ProjectBLCount> projects = context.ExpenseItems.Where(e => e.ExpenseId == ecf.Id).GroupBy(bl => bl.ProjectBudget.BudgetCategory.ProjectDonor).
                    Select(projList => new ProjectBLCount
                    {
                        ProjectDonorId = projList.FirstOrDefault().ProjectBudget.BudgetCategory.ProjectDonor.Id,
                        BudgetLineCount = projList.Count()
                    }).OrderByDescending(p => p.BudgetLineCount).ToList<ProjectBLCount>();
                //Notify requestor
                var requestor = context.Staffs.FirstOrDefault(s => s.Id == ecf.PreparedBy).Person;
                var msgBody = string.Format(NotificationHelper.ecfFundsPostedMsgBody, requestor.FirstName, ecf.RefNumber);
                notificationService.SendNotification(requestor.OfficialEmail, msgBody, NotificationHelper.ecfsubject);

                //Notify the Project Manager(s)
                foreach (var project in projects)
                {
                    var proj = context.ProjectDonors.FirstOrDefault(p => p.Id == project.ProjectDonorId);
                    var name = proj.Staff.Person.FirstName;
                    var email = proj.Staff.Person.OfficialEmail;
                    msgBody = string.Format(NotificationHelper.ecfPMNotifyFundsPostedMsgBody, name, ecf.RefNumber, proj.ProjectNumber);
                    notificationService.SendNotification(email, msgBody, NotificationHelper.ecfsubject);
                }
            }
        }

        public bool DeleteECF(Guid id)
        {
            using (var context = new SCMSEntities())
            {
                var ecf = context.ExpenseRequests.FirstOrDefault(e => e.Id == id);
                ecf.ExpenseItems.ToList().ForEach(context.ExpenseItems.DeleteObject);
                context.SaveChanges();
                context.ExpenseRequests.DeleteObject(ecf);
                return context.SaveChanges() > 0;
            }
        }

        public bool DeleteECFItem(Guid id)
        {
            using (var context = new SCMSEntities())
            {
                var ecfItem = context.ExpenseItems.FirstOrDefault(e => e.Id == id);
                var ecf = ecfItem.ExpenseRequest;
                ecf.TotalValue -= ecfItem.ItemAmount;
                ecf.TotalValue = ecf.TotalValue < 0 ? 0 : ecf.TotalValue;
                context.ExpenseItems.DeleteObject(ecfItem);
                return context.SaveChanges() > 0;
            }
        }

        public List<BudgetCheckResult> RunFundsAvailableCheck(Guid ecfId)
        {
            var bcrList = new List<BudgetCheckResult>();
            using (var context = new SCMSEntities())
            {
                var ecfItems = context.ExpenseItems.IncludeExpenseRequest().IncludeProjectDonor().Where(r => r.ExpenseId == ecfId);
                var ecf = ecfItems.ToList()[0].ExpenseRequest;
                //Construct list of project budgets for that OR
                List<ProjectBudget> pbList = new List<ProjectBudget>();
                foreach (var ecfItem in ecfItems.ToList())
                {
                    if (!pbList.Contains(ecfItem.ProjectBudget))
                        pbList.Add(ecfItem.ProjectBudget);
                }
                foreach (var pb in pbList)
                {
                    decimal totalAmount = ecfItems.Where(b => b.BudgetLineId == pb.Id).Sum(b => b.ItemAmount);
                    if (!budgetService.SufficientFundsAvailable(totalAmount, pb.Id.ToString(), ecf.ExpenseCurrencyId.ToString(), Guid.Empty, Guid.Empty, Guid.Empty))
                    {
                        decimal availableFunds = pb.TotalBudget - (decimal)(pb.TotalCommitted + pb.TotalPosted);
                        availableFunds = (decimal)exchangeRateService.GetForeignCurrencyValue(ecf.ExpenseCurrencyId, pb.BudgetCategory.ProjectDonor.CurrencyId.Value, availableFunds, ecf.CountryProgrammeId);
                        bcrList.Add(new BudgetCheckResult { ProjectBudget = pb, AmountChecked = totalAmount, AvailableAmount = availableFunds, FundsAvailable = false });
                    }
                }
            }
            return bcrList;
        }
    }
}
